package com.chen.base.utils.file;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.chen.base.utils.StreamUtil;
import com.chen.root.oclass.vo.FileAttrVO;
import com.chen.root.oclass.vo.FileStructureVO;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;

import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author: ChenJinLong
 * @DATE: 2024/12/5
 **/
@Slf4j
public class IoFileUtil {

    /** 默认5MB */
    public static final Integer DEFAULT_BYTE_SIZE = 1024 * 1024 * 5;

    /**
     * 拆分文件
     * @param filePath 被拆分的文件路径
     * @param partDirPath 分片目录路径
     */
    @SneakyThrows
    public static void partFile(String filePath, String partDirPath, Supplier<String> nameSupp){
        File file = new File(filePath);
        Assert.isTrue(file.exists(), file.getPath() + ", 文件不存在!");
        FileUtil.mkdir(partDirPath);

        String name = file.getName();
        if (nameSupp != null) {
            name = nameSupp.get();
        }

        int len, i = 0;
        byte[] bytes = new byte[DEFAULT_BYTE_SIZE];
        try (RandomAccessFile rFile = new RandomAccessFile(filePath, "r")) {
            while ((len = rFile.read(bytes)) != -1){
                // 写入数据
                String partFilePath = StrUtil.format("{}\\\\{}.S{}E.part", partDirPath, name, i++);
                try (RandomAccessFile rwFile = new RandomAccessFile(partFilePath, "rw")) {
                    rwFile.write(bytes, 0, len);
                }
            }
        }
    }

    /**
     * 合并文件
     * @param partDirPath 分片目录路径
     * @param newFilePath 新文件路径, 含文件名
     * @param isDel 是否删除分片文件
     * @param keyEx 按照某属性进行升序
     */
    @SneakyThrows
    public static void mergeFile(String partDirPath, String newFilePath, boolean isDel, Function<File, ?> keyEx){
        File newFile = new File(newFilePath);
        if (newFile.exists()){
            // 删除新目标文件
            FileUtil.del(newFile);
        }

        try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(newFile.toPath()))){
            File dirFile = new File(partDirPath);
            Assert.isTrue(dirFile.isDirectory(), dirFile.getPath() + ", 分片目录不存在!");

            // 默认按照S和E之间的数字排序
            List<File> fileList = Arrays.asList(Objects.requireNonNull(dirFile.listFiles()));
            fileList = fileList.stream()
                    .filter(f -> f.getName().contains(newFile.getName()))
                    .filter(f -> f.getName().contains(".part"))
                    .collect(Collectors.toList());
            if (keyEx != null){
                fileList = StreamUtil.sort(fileList, keyEx, true);
            }else {
                fileList = StreamUtil.sort(fileList, file -> sortSe(file.getName()), true);
            }

            // 读取分片文件, 按顺序写入到目标文件中
            for (File partFile : fileList) {
                try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(partFile.toPath()))) {
                    byte[] bytes = new byte[DEFAULT_BYTE_SIZE];
                    int len;
                    while ((len = bis.read(bytes)) != -1) {
                        bos.write(bytes, 0, len);
                    }
                }
                // 删除分片文件?
                if (isDel) {
                    FileUtil.del(partFile);
                }
            }

            bos.flush();
        }
    }

    /**
     * 多个分片文件合并为一个最终文件
     * @param filePaths 分片文件路径集
     * @param targetFilePath 合并后的文件路径
     */
    public static void mergeFiles(List<String> filePaths, String targetFilePath, boolean isDel, Function<String, ?> keyEx) {
        // 排序
        if (keyEx != null){
            filePaths = StreamUtil.sort(filePaths, keyEx, true);
        }else {
            filePaths = StreamUtil.sort(filePaths, IoFileUtil::sortSe, true);
        }

        // 如果只有一个文件，则直接合并成最终文件
        if (filePaths.size() == 1) {
            new File(filePaths.get(0)).renameTo(new File(targetFilePath));
            return;
        }

        // 利用Java nio库中FileChannel类的transferFrom方法进行合并
        try (FileChannel fosChannel = new FileOutputStream(targetFilePath, true).getChannel()){
            for (String filePath : filePaths) {
                try (FileChannel fisChannel = new FileInputStream(filePath).getChannel()){
                    fosChannel.transferFrom(fisChannel, fosChannel.size(), fisChannel.size());
                }
                if (isDel){
                    FileUtil.del(filePath);
                }
            }
        } catch (Exception e) {
            log.error("合并文件异常: ", e);
        }
    }

    /**
     * 输出给定目录下的文件，包括子目录中的文件
     * @param dirPath 目录路径
     * @param fileVo 文件结构属性信息
     */
    @SneakyThrows
    public static FileStructureVO fileStructure(String dirPath, FileStructureVO fileVo) {
        if (fileVo == null){
            fileVo = new FileStructureVO();
        }

        // 建立当前目录中文件的File对象
        File currFileDir = new File(dirPath);
        // 取得代表目录中所有文件的File对象数组
        File[] list = currFileDir.listFiles();
        if (list != null) {
            for (File file : list) {
                if (fileVo.getParentDir() == null){
                    fileVo.setParentDir(file.getParent());
                }
                if (file.isDirectory()) {
                    fileVo.getDirPathList().add(file.getPath());
                    fileStructure(file.getPath(), fileVo);
                }else {
                    FileAttrVO fileAttr = new FileAttrVO();
                    fileAttr.setFilePath(file.getPath());
                    fileAttr.setSize(new File(file.getPath()).length());
                    if (fileAttr.getSize() <= DEFAULT_BYTE_SIZE){
                        fileAttr.setMd5(IoFileUtil.md5(file.getPath()));
                    }

                    fileVo.getFileAttrs().add(fileAttr);
                }
            }
        }

        return fileVo;
    }

    /**
     * 文件转base64
     * @param filePath 文件路径
     * @return base64
     */
    @SneakyThrows
    public static String fileToBase64(String filePath) {
        byte[] bytes = Files.readAllBytes(Paths.get(filePath));
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * base64转文件
     * @param base64 base64
     * @param filePath 文件路径
     * @return 文件
     */
    @SneakyThrows
    public static File base64ToFile(String base64, String filePath) {
        byte[] bytes = Base64.getDecoder().decode(base64);
        Files.write(Paths.get(filePath), bytes, StandardOpenOption.CREATE);
        return new File(filePath);
    }

    /**
     * 文件剪切
     * @param sourceFile 源文件
     * @param targetPath 目标目录
     */
    public static void shearFile(File sourceFile, String targetPath, String fileName) throws IOException {
        FileUtil.mkdir(targetPath);
        String targetFilePath = targetPath + "\\" +  fileName;
        // 如果目标文件存在，则删除
        FileUtil.del(targetFilePath);
        // 移动文件
        Files.move(sourceFile.toPath(), Paths.get(targetFilePath), StandardCopyOption.REPLACE_EXISTING);
    }

    /**
     * 获取文件分片信息
     * @param chunkSize 分片大小
     * @param offset 偏移量
     * @param filePath 文件路径
     * @return 文件分片信息
     */
    public static byte[] getFileChunk(long offset, Integer chunkSize, String filePath) {
        byte[] buffer = new byte[chunkSize];
        try (RandomAccessFile accessFile = new RandomAccessFile(filePath, "r")) {
            // 定位到该分片的偏移量
            accessFile.seek(offset);
            // 将数据读取到buffer中
            accessFile.read(buffer);
        } catch (IOException e) {
            log.error("", e);
        }
        return buffer;
    }

    /**
     * 计算文件的MD5属性
     * @param filePath 文件路径
     * @return MD5
     */
    @SneakyThrows
    public static String md5(String filePath){
        try (InputStream is = Files.newInputStream(Paths.get(filePath))){
            return DigestUtils.md5Hex(is);
        }
    }

    /** 提取S和E之间的数值 **/
    public static Integer sortSe(File file){
        return sortSe(file.getPath());
    }

    /** 提取S和E之间的数值 **/
    public static Integer sortSe(String name){
        name = name.substring(name.lastIndexOf("S") + 1, name.lastIndexOf("E"));
        return Integer.valueOf(name);
    }

    /** 向文件末尾追加字节 **/
    @SneakyThrows
    public static void appendByte(String filePath, byte[] bytes, int len){
        try (RandomAccessFile rwFile = new RandomAccessFile(filePath, "rw")) {
            rwFile.seek(rwFile.length());
            rwFile.write(bytes, 0, len);
        }
    }

}
